package bblvolume

import (
	"crypto/md5"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
	"syscall"
	"time"
)

const (
	name = "posix"
)

func NewPosixFileSystem(uuid string, cluster_addr []string, cluster_pass, trans_addr, export string) (*LocalFileSystem, error) {
	return &LocalFileSystem{uuid: uuid, export: export, cluster_addr: cluster_addr, cluster_pass: cluster_pass, trans_addr: trans_addr, ftype: name}, nil
}

type LocalFileSystem struct {
	uuid         string
	cluster_addr []string
	cluster_pass string
	trans_addr   string
	export       string
	ftype        string
}

func _name_hash(name string) string {
	h := md5.New()
	h.Write([]byte(name))
	return fmt.Sprintf("%x", h.Sum(nil))
}

func (f *LocalFileSystem) realpath(name string) string {
	ext := filepath.Ext(name)
	hash := _name_hash(name)
	fpath := hash[0:2] + "/" + hash[2:4] + "/" + hash + ext
	return filepath.Join(f.export, fpath)
}

func (f *LocalFileSystem) UUID() string {
	return f.uuid
}
func (f *LocalFileSystem) ClusterServer() []string {
	return f.cluster_addr
}
func (f *LocalFileSystem) ClusterPassphrase() string {
	return f.cluster_pass
}
func (f *LocalFileSystem) TransAddr() string {
	return f.trans_addr
}
func (f *LocalFileSystem) Create(name string) (Object, error) {
	path := f.realpath(name)
	err := os.MkdirAll(filepath.Dir(path), os.ModePerm)
	if err != nil {
		return nil, err
	}
	file, err := os.Create(path)
	if err != nil {
		return nil, err
	}
	return &FileObject{name: name, os_path: file.Name(), file: file}, nil
}

func (f *LocalFileSystem) Open(name string, flag int) (Object, error) {
	path := f.realpath(name)
	err := os.MkdirAll(filepath.Dir(path), os.ModePerm)
	if err != nil {
		return nil, err
	}
	file, err := os.OpenFile(path, flag, os.ModePerm)
	if err != nil {
		return nil, err
	}
	return &FileObject{name: name, os_path: file.Name(), file: file}, nil
}

func (f *LocalFileSystem) Find(name string) (Object, error) {
	fi, err := os.Stat(f.realpath(name))
	if os.IsExist(err) {
		return &FileObject{name: name, os_path: fi.Name(), fi: fi}, nil
	}
	return nil, err

}

func (f *LocalFileSystem) Rename(from, to string) error {
	if strings.HasPrefix(to, "/") {
		rpath := f.realpath(to)
		if err := os.MkdirAll(filepath.Dir(rpath), os.ModePerm); err != nil {
			return err
		}
		return os.Rename(f.realpath(from), rpath)
	} else if strings.HasPrefix(to, "http://") {
		_, err := PostFile(f.realpath(from), to)
		if err != nil {
			return err
		}
		return os.Remove(f.realpath(from))
	}
	return fmt.Errorf("error path %v", to)
}

func (f *LocalFileSystem) Remove(name string) error {
	return os.Remove(f.realpath(name))
}

func (f *LocalFileSystem) Copy(from, to string) error {
	if strings.HasPrefix(to, "/") {
		return f.Link(from, to)
	} else if strings.HasPrefix(to, "http://") {
		_, err := PostFile(f.realpath(from), to)
		return err
	}
	return fmt.Errorf("error path %v", to)
}

func (f *LocalFileSystem) Link(from, to string) error {
	rpath := f.realpath(to)
	if err := os.MkdirAll(filepath.Dir(rpath), os.ModePerm); err != nil {
		return err
	}
	return os.Link(f.realpath(from), rpath)
}
func (f *LocalFileSystem) RemoveAll(path string) error {
	return UnsupportedOperation
}
func (f *LocalFileSystem) Mkdirs(path string) error {
	return UnsupportedOperation
}
func (f *LocalFileSystem) Exist(name string) bool {
	_, err := os.Stat(f.realpath(name))
	return err == nil || os.IsExist(err)
}
func (f *LocalFileSystem) Walk(path string, walkFn WalkFunc) {
	panic(UnsupportedOperation)
}
func (f *LocalFileSystem) Notify(path string, event int, notifyFn NotifyFunc) {
	panic(UnsupportedOperation)
} //订阅存储系统事件
func (f *LocalFileSystem) Stat(name string) (ObjectInfo, error) {
	fi, err := os.Stat(f.realpath(name))
	if err != nil {
		return nil, err
	}
	return &FileObjectInfo{fi}, nil
}
func (f *LocalFileSystem) String() string {
	return f.ftype + ":" + f.export
}

func (f *LocalFileSystem) StorageStat() *StorageStat {
	fs := syscall.Statfs_t{}
	err := syscall.Statfs(f.export, &fs)
	if err != nil {
		return nil
	}
	stat := &StorageStat{}
	stat.Export = f.export
	stat.TotalSpace = fs.Blocks * uint64(fs.Bsize)
	stat.FreeSpace = fs.Bfree * uint64(fs.Bsize)
	stat.UsedSpace = stat.TotalSpace - stat.FreeSpace
	stat.DevName = filepath.VolumeName(f.export)
	return stat
}

type FileObject struct {
	name    string
	os_path string
	file    *os.File
	fi      os.FileInfo
}

func (f *FileObject) Name() string {
	return f.name
}

func (f *FileObject) Read(p []byte) (n int, err error) {
	if f.file == nil {
		return 0, fmt.Errorf("%s not open", f.name)
	}
	return f.file.Read(p)
}
func (f *FileObject) ReadAt(p []byte, off int64) (n int, err error) {
	if f.file == nil {
		return 0, fmt.Errorf("%s not open", f.name)
	}
	return f.file.ReadAt(p, off)
}
func (f *FileObject) ReadFrom(r io.Reader) (n int64, err error) {
	if f.file == nil {
		return 0, fmt.Errorf("%s not open", f.name)
	}
	buf := make([]byte, 32*1024)
	for {
		nr, er := r.Read(buf)
		if nr > 0 {
			nw, ew := f.Write(buf[0:nr])
			if nw > 0 {
				n += int64(nw)
			}
			if ew != nil {
				err = ew
				break
			}
			if nr != nw {
				err = io.ErrShortWrite
				break
			}
		}
		if er == io.EOF {
			break
		}
		if er != nil {
			err = er
			break
		}
	}
	return n, err
}
func (f *FileObject) Write(p []byte) (n int, err error) {
	if f.file == nil {
		return 0, fmt.Errorf("%s not open", f.name)
	}
	return f.file.Write(p)
}
func (f *FileObject) WriteAt(p []byte, off int64) (n int, err error) {
	if f.file == nil {
		return 0, fmt.Errorf("%s not open", f.name)
	}
	return f.file.WriteAt(p, off)
}
func (f *FileObject) WriteTo(w io.Writer) (n int64, err error) {
	if f.file == nil {
		return 0, fmt.Errorf("%s not open", f.name)
	}
	buf := make([]byte, 32*1024)
	for {
		nr, er := f.Read(buf)
		if nr > 0 {
			nw, ew := w.Write(buf[0:nr])
			if nw > 0 {
				n += int64(nw)
			}
			if ew != nil {
				err = ew
				break
			}
			if nr != nw {
				err = io.ErrShortWrite
				break
			}
		}
		if er == io.EOF {
			break
		}
		if er != nil {
			err = er
			break
		}
	}
	return n, err
}
func (f *FileObject) Seek(offset int64, whence int) (int64, error) {
	if f.file == nil {
		return 0, fmt.Errorf("%s not open", f.name)
	}
	return f.file.Seek(offset, whence)
}
func (f *FileObject) Truncate(size int64) error {
	if f.file == nil {
		return fmt.Errorf("%s not open", f.name)
	}
	return f.file.Truncate(size)
}
func (f *FileObject) Close() error {
	if f.file != nil {
		return f.file.Close()
	}
	return nil
}
func (f *FileObject) Stat() (ObjectInfo, error) {
	if f.fi != nil {
		return &FileObjectInfo{f.fi}, nil
	}
	if f.file == nil {
		fi, err := os.Stat(f.os_path)
		if err != nil {
			return nil, err
		}
		f.fi = fi
		return &FileObjectInfo{fi}, nil
	}
	fi, err := f.file.Stat()
	if err != nil {
		return nil, err
	}
	f.fi = fi
	return &FileObjectInfo{fi}, nil
}

type FileObjectInfo struct {
	fileInfo os.FileInfo
}

func (f *FileObjectInfo) Name() string {
	return f.fileInfo.Name()
}
func (f *FileObjectInfo) LinkPath() string {
	return "-"
}
func (f *FileObjectInfo) IsDir() bool {
	return false
}
func (f *FileObjectInfo) IsLink() bool {
	return false
}
func (f *FileObjectInfo) IsObject() bool {
	return true
}
func (f *FileObjectInfo) Size() int64 {
	return f.fileInfo.Size()
}
func (f *FileObjectInfo) Endpoint() string {
	return "-"
} //http://uuid:9933/xxx/xxx/xxx/xxx/xxx/xxx.vod
func (f *FileObjectInfo) CreateTime() time.Time {
	return f.fileInfo.ModTime()
}
func (f *FileObjectInfo) AccessTime() time.Time {
	return f.fileInfo.ModTime()
}
func (f *FileObjectInfo) ModTime() time.Time {
	return f.fileInfo.ModTime()
}
